﻿namespace ContactsSync.Infrastructure.Behaviors
{
    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Input;
    using ContactsSync.Infrastructure.Models;

    public class ItemsControlDragDropBehavior
    {
        public static readonly DependencyProperty IsDragSourceProperty = DependencyProperty.RegisterAttached(
            "IsDragSource",
            typeof(bool), 
            typeof(ItemsControlDragDropBehavior),
            new PropertyMetadata(false, IsDragSourceChanged));

        public static readonly DependencyProperty IsDropTargetProperty = DependencyProperty.RegisterAttached(
           "IsDropTarget",
           typeof(bool),
           typeof(ItemsControlDragDropBehavior),
           new PropertyMetadata(false, IsDropTargetChanged));

        public static readonly DependencyProperty ItemTypeProperty = DependencyProperty.RegisterAttached(
           "ItemType",
           typeof(Type),
           typeof(ItemsControlDragDropBehavior),
           new PropertyMetadata(null));

        public static readonly DependencyProperty DataTemplateProperty = DependencyProperty.RegisterAttached(
           "DataTemplate",
           typeof(DataTemplate),
           typeof(ItemsControlDragDropBehavior),
           new PropertyMetadata(null, IsDataTemplateChanged));

        public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached(
            "Command", 
            typeof(ICommand),
            typeof(ItemsControlDragDropBehavior),
            new PropertyMetadata(null));

        private const int DragWaitCounterLimit = 10;
        
        private static object draggedData;

        private static bool isMouseDown;
        
        private static Point dragStartPosition;
        
        private static bool isDragging;
        
        private static DragAdorner dragAdorner;
        
        private static InsertAdorner insertAdorner;
        
        private static int dragScrollWaitCounter;

        private static DataTemplate DataTemplate { get; set; }
        
        public static void SetIsDragSource(DependencyObject dependencyObject, bool value)
        {
            dependencyObject.SetValue(IsDragSourceProperty, value);
        }

        public static bool GetIsDragSource(DependencyObject dependencyObject)
        {
            return (bool) dependencyObject.GetValue(IsDragSourceProperty);
        }

        public static void SetItemType(DependencyObject dependencyObject, Type value)
        {
            dependencyObject.SetValue(ItemTypeProperty, value);
        }

        public static Type GetItemType(DependencyObject dependencyObject)
        {
            return (Type)dependencyObject.GetValue(ItemTypeProperty);
        }

        public static void SetDataTemplate(DependencyObject dependencyObject, DataTemplate value)
        {
            dependencyObject.SetValue(DataTemplateProperty, value);
        }

        public static DataTemplate GetDataTemplate(DependencyObject dependencyObject)
        {
            return dependencyObject.GetValue(DataTemplateProperty) as DataTemplate;
        }

        public static void SetIsDropTarget(DependencyObject dependencyObject, bool value)
        {
            dependencyObject.SetValue(IsDropTargetProperty, value);
        }

        public static bool GetIsDropTarget(DependencyObject dependencyObject)
        {
            return (bool)dependencyObject.GetValue(IsDropTargetProperty);
        }

        public static void SetCommand(DependencyObject dependencyObject, ICommand command)
        {
            dependencyObject.SetValue(CommandProperty, command);
        }

        public static ICommand GetCommand(DependencyObject dependencyObject)
        {
            return (ICommand) dependencyObject.GetValue(CommandProperty);
        }

        private static void IsDataTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            DataTemplate = e.NewValue as DataTemplate;
        }

        private static void IsDragSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
       {
           ItemsControl dragSource = d as ItemsControl;

           if (dragSource != null)
           {
               if (e.NewValue.Equals(true))
               {
                   dragSource.PreviewMouseLeftButtonDown += DragSource_PreviewMouseLeftButtonDown;
                   dragSource.PreviewMouseLeftButtonUp += DragSource_PreviewMouseLeftButtonUp;
                   dragSource.PreviewMouseMove += DragSource_PreviewMouseMove;
               }
               else
               {
                   dragSource.PreviewMouseLeftButtonDown -= DragSource_PreviewMouseLeftButtonDown;
                   dragSource.PreviewMouseLeftButtonUp -= DragSource_PreviewMouseLeftButtonUp;
                   dragSource.PreviewMouseMove -= DragSource_PreviewMouseMove;
               }
           }
       }

       private static void DragSource_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
       {
           ItemsControl dragSource = sender as ItemsControl;

           if (dragSource != null)
           {
               Point point = e.GetPosition(dragSource);
               draggedData = Helper.GetDataObjectFromItemsControl(dragSource, point);

               if (draggedData != null)
               {
                   isMouseDown = true;
                   dragStartPosition = point;
               }
           }
       }

       private static void DragSource_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
       {
           ResetState();
       }

       private static void DragSource_PreviewMouseMove(object sender, MouseEventArgs e)
       {
           if (isMouseDown)
           {
               ItemsControl dragSource = sender as ItemsControl;

               if (dragSource != null)
               {
                   Point currentPosition = e.GetPosition(dragSource);
                   if ((isDragging == false) && (Math.Abs(currentPosition.X - dragStartPosition.X) > SystemParameters.MinimumHorizontalDragDistance ||
                       Math.Abs(currentPosition.Y - dragStartPosition.Y) > SystemParameters.MinimumVerticalDragDistance))
                   {
                       DragStarted(dragSource);
                   }
               }
           }
       }

       private static void DragStarted(DependencyObject itemsControl)
       {
           Type itemType = GetItemType(itemsControl);

           if (draggedData.GetType() != itemType)
           {
               return;
           }

           isDragging = true;

           DataObject dataObject = new DataObject(itemType, draggedData);

           DragDrop.DoDragDrop(itemsControl, dataObject, DragDropEffects.Copy | DragDropEffects.Move);

           ResetState();
       }

       private static void IsDropTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
       {
           ItemsControl dropTarget = d as ItemsControl;

           if (dropTarget != null)
           {
               if (e.NewValue.Equals(true))
               {
                   dropTarget.AllowDrop = true;
                   dropTarget.PreviewDragEnter += DropTarget_PreviewDragEnter;
                   dropTarget.PreviewDragOver += DropTarget_PreviewDragOver;
                   dropTarget.PreviewDrop += DropTarget_PreviewDrop;
                   dropTarget.PreviewQueryContinueDrag += DropTarget_PreviewQueryContinueDrag;
                   dropTarget.DragLeave += DropTarget_DragLeave;
               }
               else
               {
                   dropTarget.AllowDrop = false;
                   dropTarget.PreviewDragEnter -= DropTarget_PreviewDragEnter;
                   dropTarget.PreviewDragOver -= DropTarget_PreviewDragOver;
                   dropTarget.PreviewDrop -= DropTarget_PreviewDrop;
                   dropTarget.PreviewQueryContinueDrag -= DropTarget_PreviewQueryContinueDrag;
                   dropTarget.DragLeave -= DropTarget_DragLeave;
               }
           }
       }

       private static void DropTarget_PreviewDragEnter(object sender, DragEventArgs e)
       {
           ItemsControl dropTarget = sender as ItemsControl;

           if (dropTarget != null)
           {
               Type itemType = GetItemType(dropTarget);

               if (e.Data.GetDataPresent(itemType))
               {
                   object data = e.Data.GetData(itemType);

                   InitializeDragAdorner(dropTarget, data, e.GetPosition(dropTarget));
                   InitializeInsertAdorner(dropTarget, e);
               }
           }

           e.Handled = true;
       }

       private static void DropTarget_PreviewDragOver(object sender, DragEventArgs e)
       {
           ItemsControl dropTarget = sender as ItemsControl;

           if (dropTarget != null)
           {
               Type itemType = GetItemType(dropTarget);

               if (e.Data.GetDataPresent(itemType))
               {
                   UpdateDragAdorner(e.GetPosition(dropTarget));
                   UpdateInsertAdorner(dropTarget, e);
                   HandleDragScrolling(dropTarget, e);
               }
           }

           e.Handled = true;
       }

       private static void DropTarget_PreviewDrop(object sender, DragEventArgs e)
       {
           ItemsControl dropTarget = sender as ItemsControl;

           if (dropTarget != null)
           {
               Type itemType = GetItemType(dropTarget);
               DetachAdorners();
               e.Handled = true;

               if (e.Data.GetDataPresent(itemType))
               {
                   object itemToAdd = e.Data.GetData(itemType);
                   
                   if ((e.KeyStates & DragDropKeyStates.ControlKey) != 0 &&
                       Helper.DoesItemExists(dropTarget, itemToAdd))
                   {
                       if (MessageBox.Show("Item already exists. Do you want to overwrite it?", "Copy File", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
                       {
                           Helper.RemoveItem(dropTarget, itemToAdd);
                       }
                       else
                       {
                           e.Effects = DragDropEffects.None;
                           return;
                       }
                   }

                   e.Effects = ((e.KeyStates & DragDropKeyStates.ControlKey) != 0)
                                   ?
                                       DragDropEffects.Move
                                   : DragDropEffects.Copy;

                   // Helper.AddItem(dropTarget, itemToAdd, FindInsertionIndex(dropTarget, e));
                   int index = FindInsertionIndex(dropTarget, e);

                   ICommand command = GetCommand(dropTarget);

                   ContactInfo contactInfo = new ContactInfo(index, (Contact)itemToAdd);

                   command.Execute(contactInfo);
               }
               else
               {
                   e.Effects = DragDropEffects.None;
               }
           }
       }

       private static void DropTarget_PreviewQueryContinueDrag(object sender, QueryContinueDragEventArgs e)
       {
           if (e.EscapePressed)
           {
               e.Action = DragAction.Cancel;
               ResetState();
               DetachAdorners();
               e.Handled = true;
           }
       }

       private static void DropTarget_DragLeave(object sender, DragEventArgs e)
       {
           DetachAdorners();
           e.Handled = true;
       }

        private static void ResetState()
       {
           isMouseDown = false;
           isDragging = false;
           draggedData = null;
           dragScrollWaitCounter = DragWaitCounterLimit;
       }

        private static void InitializeDragAdorner(UIElement itemsControl, object dragData, Point startPosition)
        {
            if (dragAdorner == null)
            {
                AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(itemsControl);
                dragAdorner = new DragAdorner(dragData, DataTemplate, itemsControl, adornerLayer);
                dragAdorner.UpdatePosition(startPosition.X, startPosition.Y);
            }
        }

        private static void InitializeInsertAdorner(ItemsControl itemsControl, DragEventArgs e)
        {
            if (insertAdorner == null)
            {
                AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(itemsControl);
                UIElement itemContainer = Helper.GetItemContainerFromPoint(itemsControl, e.GetPosition(itemsControl));
                if (itemContainer != null)
                {
                    bool isPointInTopHalf = Helper.IsPointInTopHalf(itemsControl, e);
                    bool isItemsControlOrientationHorizontal = Helper.IsItemControlOrientationHorizontal(itemsControl);
                    insertAdorner = new InsertAdorner(isPointInTopHalf, isItemsControlOrientationHorizontal, itemContainer, adornerLayer);
                }
            }
        }

        private static void UpdateDragAdorner(Point currentPosition)
        {
            if (dragAdorner != null)
            {
                dragAdorner.UpdatePosition(currentPosition.X, currentPosition.Y);
            }
        }

        private static void UpdateInsertAdorner(ItemsControl itemsControl, DragEventArgs e)
        {
            if (insertAdorner != null)
            {
                insertAdorner.IsTopHalf = Helper.IsPointInTopHalf(itemsControl, e);
                insertAdorner.InvalidateVisual();
            }
        }

        private static void HandleDragScrolling(ItemsControl itemsControl, DragEventArgs e)
        {
            bool? isMouseAtTop = Helper.IsMousePointerAtTop(itemsControl, e.GetPosition(itemsControl));
            
            if (isMouseAtTop.HasValue)
            {
                if (dragScrollWaitCounter == DragWaitCounterLimit)
                {
                    dragScrollWaitCounter = 0;
                    ScrollViewer scrollViewer = Helper.FindScrollViewer(itemsControl);
                    if (scrollViewer != null && scrollViewer.CanContentScroll
                        && scrollViewer.ComputedVerticalScrollBarVisibility == Visibility.Visible)
                    {
                        if (isMouseAtTop.Value)
                        {
                            scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - 1.0);
                        }
                        else
                        {
                            scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + 1.0);
                        }

                        e.Effects = DragDropEffects.Scroll;
                    }
                }
                else
                {
                    dragScrollWaitCounter++;
                }
            }
            else
            {
                e.Effects = ((e.KeyStates & DragDropKeyStates.ControlKey) != 0) ?
                               DragDropEffects.Copy : DragDropEffects.Move;
            }
        }

        private static void DetachAdorners()
        {
            if (insertAdorner != null)
            {
                insertAdorner.Destroy();
                insertAdorner = null;
            }

            if (dragAdorner != null)
            {
                dragAdorner.Destroy();
                dragAdorner = null;
            }
        }

        private static int FindInsertionIndex(ItemsControl itemsControl, DragEventArgs e)
        {
            UIElement dropTargetContainer = Helper.GetItemContainerFromPoint(itemsControl, e.GetPosition(itemsControl));
           
            if (dropTargetContainer != null)
            {
                int index = itemsControl.ItemContainerGenerator.IndexFromContainer(dropTargetContainer);
                if (Helper.IsPointInTopHalf(itemsControl, e))
                {
                    return index;
                }
                else
                {
                    return index + 1;
                }
            }

            return itemsControl.Items.Count;
        }
    }
}
